/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.core.component;

import com.vii.brillien.core.component.spi.AbstractState;
import com.vii.brillien.core.component.spi.states.DefaultErrorState;
import com.vii.brillien.core.component.spi.states.DefaultExitState;
import com.vii.brillien.core.component.spi.states.DefaultInitState;
import com.vii.brillien.core.management.BrillienError;
import com.vii.brillien.core.management.BrillienServices;
import com.vii.brillien.core.management.CouchDBServices;
import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.annotations.NullResponseAllowed;
import com.vii.brillien.kernel.annotations.PresenceService;
import com.vii.brillien.kernel.annotations.sso.Authenticator;
import com.vii.brillien.kernel.annotations.sso.Permission;
import com.vii.brillien.kernel.annotations.sso.Role;
import com.vii.brillien.kernel.axiom.atomic.*;
import com.vii.brillien.kernel.axiom.component.State;
import com.vii.brillien.kernel.axiom.component.StateDivision;
import com.vii.brillien.kernel.axiom.component.Validable;
import com.vii.brillien.kernel.axiom.sso.Session;
import com.vii.brillien.kernel.axiom.transport.Communication;
import com.vii.streamline.annotation.P;
import com.vii.streamline.services.OperationServices;
import com.vii.streamline.services.ThreadServices;
import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.reflection.ReflectionServices;
import com.vii.streamline.structures.collections.InnerList;
import com.vii.streamline.structures.collections.InnerMap;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import static com.vii.streamline.services.reflection.ReflectionServices.invokeMethodALike;

/**
 * Parent class for every Presence in Brillien
 */
public class AbstractPresence<R> implements Presence<BrillienCommunication, R> {

    /**
     * Name of the Presence
     */
    protected String                                                    name;

    /**
     * Timeout measure of the Presence's Aspirer activity
     */
    protected long                                                      timeMeasure;

    /**
     * Defines if the Presence need to be executed periodically
     */
    protected boolean                                                   periodical;
    
    /**
     * Future of the Aspirer activity of this Presence
     */
    protected Future<R>                                                 future;

    /**
     * Indicates if the Presence is started as an Aspirer
     */
    protected boolean                                                   submited;

    /**
     * Indicates if the Presence is waiting for Input data to be activated
     */
    protected boolean                                                   waitingForInputData;

    /**
     * Indicates if the Presence is started as an Aspirer
     */
    protected boolean                                                   messageListening;


    /**
     * Indicates the need for logging all activities to the enclosing Flow
     */
    protected boolean                                                   needToLog;

    /**
     * Indicates the need for logging all system activities to the enclosing Flow
     */
    protected boolean                                                   needToSystemLog;

    /**
     * Indicates the need for logging exceptions to the console always
     */
    protected boolean                                                   needExtraLog;

    /**
     * Indicates the need for reporting all activities to the enclosing Flow
     */
    protected boolean                                                   needToReport;

    /**
     * Flow presence which the given Presence is part of
     */
    protected Flow                                                      flow;

    /**
     * Collection of superunits.
     */
    protected ConcurrentLinkedQueue<Unit>                               superUnits;

    /**
     * PresenceManager of this Presence
     */
    protected PresenceManager<AbstractPresence,BrillienCommunication>   presenceManager;

    
    /**
     * Built-in State for closing operation
     */
    protected AbstractState                                             defaultInitState;

    /**
     * Built-in State for closing operation
     */
    protected AbstractState                                             defaultExitState;

    /**
     * Built-in State for closing operation
     */
    protected AbstractState                                             defaultErrorState;

    /**
     * List of Presence's possible states
     */
    protected ConcurrentLinkedQueue<State>                              states;

    /**
     * For every state a list is created to store the Presences that are interested in state changing events
     */
    protected ConcurrentHashMap<State, ConcurrentLinkedQueue>           stateNotifyQueue;

    /**
     * List of the Presence's actual states
     */
    protected ConcurrentLinkedQueue<State>                              actualStates;

    /**
     * Presence's StateDivision object
     */
    protected StateDivision                                             stateDivision;

    /**
     * Tells whethe the actual presence is waiting for response to a previously sent message
     */
    protected boolean                                                   waitingForResponse;


    {
        reset();
    }


    /**
     * Setter method for the field future
     */
    protected <T extends Future> void setFuture(T f){
        this.future = f;
    }

    /**
     * Getter method for field presenceManager
     */
    public PresenceManager<AbstractPresence,BrillienCommunication> getPresenceManager(){
        return presenceManager;
    }

    /**
     * Setter method for field presenceManager
     */
    public void setPresenceManager( PresenceManager manager ){
        this.presenceManager = manager;
    }

    @Override
    public Flow getFlow() {
        return flow;
    }

    @Override
    public String getFlowID() {
        return (flow == null ? null : flow.getFlowID());
    }

    @Override
    public void setFlow(Flow flow) throws BrillienException{
        if( flow != null){        
            flow.addPresences( this );
            this.flow = flow;
        }
    }

    @Override
    public Collection<Unit> getSuperUnits() {
        return superUnits;
    }

    @Override
    public void addSuperUnits(Unit... ses) throws BrillienException {
        systemlog( "Adding superunits to:: " + this.getName() );
        for( Unit unit : ses)
            if( !superUnits.contains( unit ) )
                superUnits.add( unit );
    }

    @Override
    public void removeSuperUnits(Unit... ses) throws BrillienException {
        systemlog( "Removing superunits to:: " + this.getName() );
        for( Unit unit : ses)
            superUnits.remove( unit );
    }

    @Override
    public void clearSuperUnits() throws BrillienException {
        superUnits.clear();
    }

    @Override
    public void retrieve() throws BrillienException {
        this.presenceManager.retrieveReferences( this );
    }

    @Override
    public void activateAll() throws BrillienException {
        if( !waitingForInputData )
            activateCallable();
        activateMessaging();
    }

    @Override
    public void passivateAll() throws BrillienException {
        passivateCallable();
        passivateMessaging();
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getFullName(){
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public long getAspirationTimeMeasure() {
        return timeMeasure;
    }

    @Override
    public void setAspirationTimeMeasure(long timeMeasure) {
        this.timeMeasure = timeMeasure;
    }

    @Override
    public boolean isAspirationPeriodical(){
        return periodical;
    }

    @Override
    public void setAspirationPeriodical( boolean periodical ){
        this.periodical = periodical;
    }

    @Override
    public void aspirationTimeIsUp() {
        systemlog( "Time is up by " + this.getName() );
    }

    @Override
    public void reset(){
        systemlog( "Resting presence: " + this.getName() );

        periodical = false;

        needToLog = needToReport = true;

        needToSystemLog = false;

        needExtraLog = false;

        waitingForInputData = false;

        waitingForResponse = false;

        flow = BrillienServices.SUPREME_FLOW;

        initStateMashine();

        name = this.getClass().getSimpleName();

        superUnits = new ConcurrentLinkedQueue<Unit>();
    }

    protected void report(String message) {
        report( message, null );
    }
    protected void report(String message, List<Object> params) {
        if( flow != null )
            flow.report( this, message, params );
    }


    /**
     * This method used by inner services of the application server to log exceptions
     * @param sourceClass sourceClass
     * @param sourceMethod sourceMethod
     * @param thrown exception occured
     */
    protected void exceptionLog( String sourceClass, String sourceMethod, Throwable thrown ){
        if( BrillienContext.mainLogger != null ) {
            BrillienContext.mainLogger.throwing( sourceClass, sourceMethod, thrown );
            if( needExtraLog )
                thrown.printStackTrace();
        } else
            thrown.printStackTrace();
    }

    /**
     * Inner log method invoked by all activity methods of a Presence.
     * @param message message of the log record
     */
    protected void log( String message ){
        log(message, null);
    }
    protected void log( String message, List<Object> parameters ){
        if(
            flow != null /*&& flow != ServerStatics.RESIDENT_FLOW && flow != ServerStatics.PRIMAL_FLOW*/ && needToReport
        ){
            flow.report( this, message, parameters );
        }
    }

    /**
     * Inner log method invoked by all systen activity methods of a Presence.
     * @param message message of the log record
     */
    protected void systemlog( String message ){
        systemlog(message, null);
    }
    protected void systemlog( String message, List<Object> parameters ){
        if(
                flow != null /*&& flow != ServerStatics.RESIDENT_FLOW && flow != ServerStatics.PRIMAL_FLOW*/ && needToReport
                ){
            flow.report( this, message, parameters );
        }
    }

    /*
        -------------------------------------
                Aspirer behavior
        -------------------------------------
     */
    @Override
    public void plan() throws BrillienException { }

    @Override
    public boolean isWaitingForInputData(){
        return waitingForInputData;
    }

    /**
     * Scheduling method for Aspirer when activating it
     */
    protected void innerActivateCallable(){
        setFuture( periodical
                ? ThreadServices.getStpe().scheduleWithFixedDelay( new Runnable(){
                        @Override
                        public void run() {
                            call();
                        }
                    },
                    timeMeasure, timeMeasure, TimeUnit.MILLISECONDS
                    )
                : ThreadServices.getStpe().submit( (Callable<R>)this )
        );
        systemlog( "Single callable process activated..." );

        if( !periodical && timeMeasure != PresenceService.DEFAULT_TIMEMEASURE ){
            systemlog( "Timout scheduling activated..." );
            ThreadServices.getStpe().schedule( new Runnable(){
                public void run() {
                    aspirationTimeIsUp();
                }
            }, timeMeasure, TimeUnit.MILLISECONDS );
        }
        
    }

    @Override
    public void activateCallable() throws BrillienException {
        systemlog( "Trying to activate.", new InnerList<Object>( submited, waitingForInputData ) );

        if( submited ) return;

        innerActivateCallable( );

        submited = true;
    }

    @Override
    public void passivateCallable() throws BrillienException {
        if( submited ) {
            future.cancel( true );
        }
    }

    @Override
    public boolean hasActivity() {
        return submited && !future.isDone() && !future.isCancelled();
    }

    @Override
    public R getResult() {
        try {
            return future.isDone() ? future.get() : null;
        } catch (Exception e) {
            panic( "getResult calling exception" );
        }
        return null;
    }

    /**
     * Aspirer activity as a Callable instance
     * @throws BrillienException
     */
    protected R innerCall() throws BrillienException{ return null; }

    @Override
    public R call() { R v = null;
        systemlog( "Starting acting.", new InnerList<Object>( this.toString() ) );
        
        try {
            v = innerCall();
        } catch( Throwable t ){
            //panic( "Apsirer executing problem" );
            exceptionLog( this.getClass().getName(), "call", t );
        }

        systemlog( "Acting process finished", new InnerList<Object>( this.toString() ) );

        return v;
    }

    /*
        -------------------------------------
                Sensor behavior
        -------------------------------------
     */
    
    protected String getSubject( Communication message ){
        return  message.getMessageType() == Communication.TYPE_RESULT
                ? message.getRedirectMessage()
                : message.getSubject();
    }

    protected void validateMessage( Object... parameters ) throws BrillienException {
        for( Object o : parameters ){
            if( o != null && Validable.class.isAssignableFrom( o.getClass() ) )
                ( (Validable)o ).checkValidity();
        }
    }

    protected void validate( BrillienCommunication comm, String errorMessage ) throws BrillienException {
        Boolean b = comm.getResponse();
        if( !b )
            throw new BrillienException( errorMessage );
    }
    
    protected void authorizeCall( String sender, Session session, Method m ) throws BrillienException { String ssoPresence;
        if( m != null && (ssoPresence = getSSOPresenceName()) != null ){
            if( this.getName().equalsIgnoreCase( ssoPresence ) )
                return;
            if( m.getAnnotation( Authenticator.class ) != null )
                return;

            if( session == null ){
                if( !BrillienServices.isSystemEntity( this, sender ) )
                    throw new BrillienException( "Missing Session for communication : " + this.getName() + ":" + m.getName() );
            }
            else{
                validate( sendGet(
                    ssoPresence,
                    "isAuthenticated",
                    new InnerMap<String, Object>( "session", session )
                ), "Not authenticated!" );

                Role r = m.getAnnotation( Role.class );
                Permission p = m.getAnnotation( Permission.class );

                if( r != null ){
                    validate( sendGet(
                        ssoPresence,
                        "hasARole",
                            new InnerMap<String, Object>( "session", session, "roleIdentifiers", new InnerList<String>( r.value() ) )
                    ), "Has no proper role for communication : " + this.getName() + ":" + m.getName() );
                }
                if( p != null ){
                    validate( sendGet(
                        ssoPresence,
                        "hasAPermission",
                            new InnerMap<String, Object>( "session", session, "permissions", new InnerList<String>( p.value() ) )
                    ), "Has not proper permission for communication : " + this.getName() + ":" + m.getName() );
                }
            }
        }
    }

    protected Object invokeRequestProcessor( BrillienCommunication message ) throws BrillienException {
        try{
            String msg = getSubject( message );
            Method m = ReflectionServices.getMethod( this.getClass(), msg );
            Object result = null;

            if( m != null ){
                authorizeCall(message.getSender(), message.getSession(), m);

                Object[] parameters = null;
                try{
                    parameters = message.acquireParameters( m );
                } catch( Throwable t ){ throw new BrillienException( "Cannot map parameters", t ); }

                validateMessage( parameters );
                result = invokeMethodALike( m, this, parameters ); // invokeMethod( m, this, parameters );
            }
            else {
                m = ReflectionServices.getMethod( this.getClass(), "processMessage" );

                authorizeCall( message.getSender(), message.getSession(), m );

                result = invokeMethodALike(m, this, // invokeMethod( m, this,
                    new InnerList( msg,
                        message.acquireParameters( m )
                    ).asArray()
                );
            }

            return  result != null
                    ? result
                    : ( m.getAnnotation( NullResponseAllowed.class ) != null
                        ? BrillienServices.NULL_ANSWER
                        : null
                      );
        } catch (BrillienException be) {
            throw be;
        } catch (Throwable t) {
            if( t.getCause() != null ){
                if (t.getCause() instanceof BrillienException) 
                    throw (BrillienException)t.getCause();
                if (t.getCause() instanceof StreamLineException){
                    StreamLineException se = (StreamLineException)t.getCause();
                    throw new BrillienException( se.getErrorCode(), se.getMessage(), se.getPayLoad() );
                }
            }
            throw new BrillienException( StreamLineException.ERROR_SERVER_ERROR, t );
        } finally{
            if( getPresenceManager() != null && getPresenceManager().getManagementType() == Presence.SPARKLE )
                getPresenceManager().retrieveReferences( this );
        }
    }

    protected Object invokeResultProcessor( BrillienCommunication message  ) throws BrillienException {
        try {
            String msg = getSubject( message );
            Method m = ReflectionServices.getMethod( this.getClass(), msg );

            if( message.getOriginalType() == Communication.TYPE_AGET )
                flow.report( this, BrillienServices.REPORT_CLOSED_ASYNC_COMMUNICATION );

            if( m != null ){
                authorizeCall( message.getSender(), message.getSession(), m );
                
                Object response = null;
                try{
                    Class type = m.getParameterTypes()[0];
                    response = message.acquireResponse( type );
                } catch( Throwable t ){ t.printStackTrace(); throw new BrillienException( "Cannot map parameters", t ); }

                validateMessage( response );

                return invokeMethodALike( m, this, response ); //invokeMethod( m, this, response );
            }
            else{
                m = ReflectionServices.getMethod( this.getClass(), "processMessage" );

                authorizeCall( message.getSender(), message.getSession(), m );

                return invokeMethodALike( m, this, // invokeMethod( m, this,
                    new InnerList( msg, new Object[]{ message.getResponse() } ).asArray()
                );
            }
        } catch (BrillienException be) {
            throw be;
        } catch (Throwable t) {
            throw new BrillienException( StreamLineException.ERROR_SERVER_ERROR, t );
        } finally{
            if( getPresenceManager() != null && getPresenceManager().getManagementType() == Presence.SPARKLE )
                getPresenceManager().retrieveReferences( this );
        }
    }

    /**
     * Tries to invoke the proper messageprocessor method described in the text message.
    */
    protected Object invokeMessageProcessor( BrillienCommunication message ) throws BrillienException {
        return  message.getMessageType() == Communication.TYPE_RESULT
                ? invokeResultProcessor( message )
                : invokeRequestProcessor( message );
    }


    @Override
    public Object communicationReceived( BrillienCommunication message  ) throws BrillienException{
        if( !messageListening ) throw new BrillienException( "This Presence is not interested in messaging.");

        Object result = null;

        systemlog(  "Communication received : " + message.getSubject() );

        try {
            result = invokeMessageProcessor( message );

            if( !this.isInValidStates() )
                throw new BrillienException( StreamLineException.ERROR_ILLEGAL_INNER_STATE, "Presence is not in valid states", defaultErrorState.getName() );
        } catch (BrillienException be) {
            sendError( message, be.getErrorCode(), be.getMessage(), be.getValue() );
            exceptionLog( this.getClass().getSimpleName(), "communicationReceived", be );
            return new BrillienError( be.getErrorCode(), be.getMessage(), be.getValue() );
        } catch (Throwable t) {
            sendError( message, StreamLineException.ERROR_SERVER_ERROR, t.getMessage() );
            exceptionLog( this.getClass().getSimpleName(), "communicationReceived", t );
            return new BrillienError( StreamLineException.ERROR_SERVER_ERROR, t.getMessage() );
        }

        // anything but result
        if( message.getMessageType() == Communication.TYPE_GET ||
            message.getMessageType() == Communication.TYPE_AGET
        ){
            if( result != null ){
                sendResponse(
                    message,
                    result != BrillienServices.NULL_ANSWER
                        ? result
                        : null
                );
            }
        }
        else if( message.getMessageType() == Communication.TYPE_DELEGATED_SET  ){
            if( result != null ){
                sendResponse(
                        message,
                        result != BrillienServices.NULL_ANSWER
                                ? result
                                : null
                );
            }
        }
        else if( message.getMessageType() == Communication.TYPE_SET ){
            
        } else if( message.getMessageType() == Communication.TYPE_RESULT ){

        }

        return result;
    }

    @Override
    public Object errorReceived( BrillienCommunication message ) throws BrillienException{
        exceptionLog( this.getClass().getName(), "errorReceived", new BrillienException( message.<String>getResponse() ) );

        if( message.getOriginalType() == Communication.TYPE_AGET )
            flow.report( this, BrillienServices.REPORT_CLOSED_ASYNC_COMMUNICATION );

        return new BrillienError( message.getErrorCode(), message.<String>getResponse(), message.getErrorValue() );
    }

    @Override
    public void activateMessaging() throws BrillienException {
        messageListening = true;
    }

    @Override
    public void passivateMessaging() throws BrillienException {
        messageListening = false;
    }

    @Override
    public void presenseChangedState(@P(name="entityName")String entityName, @P(name="oldState")State oldState, @P(name="newState")State newState ) throws BrillienException{
        systemlog( "presenseChangedState", new InnerList<Object>( entityName, oldState, newState ) );
    }


    /*
        -------------------------------------
                Progressor behavior
        -------------------------------------
     */


    @Override
    public boolean hasStateMachine() {
        return false;
    }

    /**
     * Initializer method to put the default states into the collection of possible states
     */
    protected void initStateStructure(){
        states = new ConcurrentLinkedQueue<State>();
        states.add( defaultExitState = new DefaultExitState(AbstractState.COLLABLE_EXIT_NAME) );
        states.add( defaultInitState = new DefaultInitState(AbstractState.COLLABLE_INIT_NAME) );
        states.add( defaultErrorState = new DefaultErrorState(AbstractState.COLLABLE_ERROR_NAME) );
    }

    /**
     * Builds up the possible states' space, empty method by default
     */
    protected void buildStateStructure(){ }


    protected State getDefaultState(){
        return defaultExitState;
    }

    /**
     * Internal method to build up the statemachine
     */
    protected void buildStateSpace(){
        stateNotifyQueue = new ConcurrentHashMap<State, ConcurrentLinkedQueue>();
        for( State state : states )
            stateNotifyQueue.put( state, new ConcurrentLinkedQueue<String>() );

        actualStates = new ConcurrentLinkedQueue<State>();
        
        actualStates.add(getDefaultState());
    }

    @Override
    public void initStateMashine() {
        initStateStructure();

        buildStateStructure();

        buildStateSpace();
    }

    @Override
    public Collection<State> getPotentialStates() {
        return states;
    }

    @Override
    public String printErrorStates( ){
        StringBuilder sb = new StringBuilder();
        for(State a : actualStates)
            if( a.isError() )
                sb.append( " " + a.getName() );
        return  sb.length() > 0
                ? this.getPresenceManager().getName() + "{ " + sb.toString() + " }"
                : "";
    }

    @Override
    public String getSSOPresenceName(){
        return getPresenceManager().getSSOPresenceName();
    }

    @Override
    public String getApiKeyPresenceName() {
        return BrillienServices.liaison != null ? BrillienServices.liaison.getApiKeyPresenceName() : null;
    }

    @Override
    public <S> S getSharing(Object key){
        for( Unit u : superUnits ){
            if( u instanceof Context){
                Context c = (Context)u;
                if( c.hasSharing( this, key ) )
                    return (S)c.getSharing( this, key );
            }
            else {
                Object o = null;
                if( (o = u.getSharing( key )) != null )
                    return (S)o;
            }
        }
        return null;
    }

    @Override
    public boolean isInValidStates() {
        for(State a : actualStates)
            if( a.isError() )
                return false;
        return true;
    }

    @Override
    public boolean isInExitState() {
        for(State a : actualStates)
            if( a.isExit() )
                return true;
        return false;
    }

    @Override
    public boolean isInState( State state ){
        return actualStates.contains( state );
    }

    @Override
    public Collection<State> getActualStates() {
        return actualStates;
    }

    @Override
    public StateDivision getStateDivision() {
        return stateDivision;
    }

    @Override
    public void setStateDivision(StateDivision stateDivision) {
        this.stateDivision = stateDivision;
    }

    protected void innerChangeState( State from, State to ) throws BrillienException {
        actualStates.remove( from ); actualStates.add( to );

        for( String recipient : (ConcurrentLinkedQueue<String>)stateNotifyQueue.get( to ) ){
            sendSet( recipient, "presenseChangedState", new InnerMap<String, Object>("entityName", this.getName(), "oldState", from, "newState", to) );
        }
    }

    @Override
    public void changeState( State from, State to ) throws BrillienException{
        if( !from.equals( to ) && actualStates.contains(from) &&
            ( stateDivision == null || to.equals(defaultExitState) || to.equals(defaultInitState) || to.equals(defaultErrorState) ) &&
            ( stateDivision == null || stateDivision.isValidTransition(from, to) )
        ){
            innerChangeState( from, to );
        }
    }

    @Override
    public void addState( State state ) throws BrillienException {
        if( !actualStates.contains( state ) && states.contains( state ) )
            actualStates.add( state );
    }

    @Override
    public void panic( String message ){
        try {
            defaultErrorState.setName( message );
            addState( defaultErrorState );
        } catch (BrillienException e) {
            exceptionLog( this.getClass().getName(), "panic", e );
        }
        exceptionLog( this.getClass().getName(), "panic", new BrillienException("Presence is panicing : " + message ) );
    }

    @Override
    public void removeState( State state ) throws BrillienException {
        if( actualStates.contains( state ) )
            actualStates.remove( state );
    }

    @Override
    public void registerStateChangeNotification(State state, String recipient) throws BrillienException {
        systemlog( "Register signer reraccord point...", new InnerList<Object>( this.toString(), state.getName(), recipient ) );

        if( state == null || !states.contains( state ) )
            throw new BrillienException("Invalid parameters");

        stateNotifyQueue.get( state ).add(recipient);
    }

    @Override
    public void unregisterStateChangeNotification(State state, String recipient) throws BrillienException {
        systemlog( "Unregister signer reraccord point...", new InnerList<Object>( this.toString(), state.getName(), recipient ) );

        if( state == null || !states.contains( state ) )
            throw new BrillienException("Invalid parameters");

        stateNotifyQueue.get( state ).remove( recipient );
    }

    protected BrillienCommunication getSharedMessage(){
        return (BrillienCommunication)BrillienServices.SERVICES_FLOW.getSharing( this, Thread.currentThread() );
    }

    protected Object getReference( Object o, String ref, String to ) throws BrillienException {
        if( o instanceof Context ){
            Context c = (Context)o;
            if( c.hasSharing( this, ref ) ){
                Object r = c.getSharing( this, ref );
                if( r instanceof String || r instanceof Presence )
                    return r;
            }
        }
        else if( o instanceof Unit ){
            Unit u = (Unit)o;
            Collection<Unit> subs = u.getSubUnits();
            for( Unit su : subs )
                if( su.getName().equals(ref) ){
                    return su;
                }
            Collection<Presence> presences = u.getPresences();
            for( Presence p : presences )
                if( p.getName().equals(ref) ){
                    return p;
                }
        }
        throw new BrillienException("Cannot found required path: " + to );
    }
    
    protected String getReference( String to ) throws BrillienException {
        String[] path = to.substring( 1 ).split( BrillienServices.REFERENCE_DELIMETER );

        if( path.length==0 )
            throw new BrillienException("Invalid path: " + to);

        Object o = getSharing( path[0] );

        if( o == null )
            throw new BrillienException("First point of the reference path must be available on the context of the presence: " + to);

        String[] rPath = OperationServices.tail( path, 1, String.class );

        for( String ref : rPath ){
            o = getReference( o, ref, to );
        }

        if( !(o instanceof String) )
            throw new BrillienException("Inconvenient sharing found: " + o.toString() );

        return o.toString();
    }

    protected boolean isReference( String to ) throws BrillienException {
        return to.startsWith(BrillienServices.REFERENCE_DELIMETER);
    }

    protected Flow getFlowToReport(){
        return getFlow();
    }


    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    //              SEND METHODS
    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    protected boolean containsKey( Map<String, Object> parameters, String key ){
        for (String s : parameters.keySet()) {
            if( s.equalsIgnoreCase( key ) )
                return true;
        }
        return false;
    }

    protected Object get( Map<String, Object> parameters, String key ){
        for (String s : parameters.keySet()) {
            if( s.equalsIgnoreCase( key ) )
                return parameters.get( s );
        }
        return null;
    }
    /*
    protected Object[] getParameters( String to, String message, Map<String, Object> parameters ) throws BrillienException {
        systemlog( "Converting map to parameter list to" + to + " about " + message, new InnerList<Object>( parameters ) );

        BrillienCommunication c = sendGet( to, "getMessageProcessorParameterNames", message );
        List<String> formalParameterNames = c.acquireResponse( List.class );

        systemlog( "Retrieved formalParameterNames" , new InnerList<Object>( formalParameterNames ) );

        c = sendGet( to, "getMessageProcessorParameterTypes", message );
        List<String> formalParameterTypes = c.acquireResponse( List.class );

        systemlog( "Retrieved formalParameterTypes", new InnerList<Object>( formalParameterTypes ) );

        Object[] os = new Object[ formalParameterNames.size() ];
        for( int i=0; i<os.length; ++i ){
            if( !containsKey( parameters, formalParameterNames.get(i) ) )
                throw new BrillienException( "No parameter found to this formal parameter:" + formalParameterNames.get(i) );

            try{
                Object o = get( parameters, formalParameterNames.get(i) );

                systemlog( "Found parameter " + o + " for " + formalParameterNames.get(i) );

                os[ i ] =
                        (
                            o instanceof HashMap &&
                            !formalParameterTypes.get(i).equals( HashMap.class.getName() )
                        )
                    ? TypeServices.fillObject( (HashMap)o, this.getClass().getClassLoader().loadClass( formalParameterTypes.get(i) ) )
                    : o;

                systemlog( "Added parameter " + os[ i ] );
            } catch( Exception e ){
                throw new BrillienException("Conversion cannot be made: " + formalParameterNames.get(i) + " " + formalParameterTypes.get(i), e );
            }
        }

        return os;
    }
    */

    @Override
    public BrillienCommunication sendGet( String to, String message, Map<String, Object> parameters ) throws BrillienException{
        return this.sendGet( BrillienContext.COMMUNICATION_REPLY_TIMEOUT, to, message, parameters);
    }

    @Override
    public BrillienCommunication sendGet( long timeout, String to, String message, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication sharedMessage = getSharedMessage();

        BrillienCommunication rpc = createBrillienCommunication( Communication.TYPE_GET, sharedMessage, to, message, null, null, parameters );

        getPresenceManager().markMessageToSend( sharedMessage, rpc );

        return presenceManager.getMediator().sendCommunication( timeout, rpc );
    }

    @Override
    public void sendAGet( String to, String message, String redirectMessage, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication sharedMessage = getSharedMessage();

        BrillienCommunication rpc = createBrillienCommunication( Communication.TYPE_AGET, sharedMessage, to, message, null, redirectMessage, parameters );

        getPresenceManager().markMessageToSend( sharedMessage, rpc );

        presenceManager.getMediator().sendCommunication( rpc );

        getFlowToReport().report(this, BrillienServices.REPORT_NEW_ASYNC_COMMUNICATION);
    }

    @Override
    public void sendDelegatedSet( String to, String message, String redirectEntityName, String redirectMessage, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication sharedMessage = getSharedMessage();

        BrillienCommunication rpc = createBrillienCommunication( Communication.TYPE_DELEGATED_SET, sharedMessage, to, message, redirectEntityName, redirectMessage, parameters );

        presenceManager.getMediator().sendCommunication( rpc );
    }

    @Override
    public void sendSet( String to, String message, Map<String, Object> parameters ) throws BrillienException{
        if( presenceManager != null && presenceManager.getMediator() != null ){
            BrillienCommunication rpc = createBrillienCommunication( Communication.TYPE_SET, getSharedMessage(), to, message, null, null, parameters );

            presenceManager.getMediator().sendCommunication( rpc );
        }
    }

    protected BrillienCommunication createBrillienCommunication(
            int messageType, BrillienCommunication sharedMessage, String to, String message, String redirectEntityName, String redirectMessage, Map<String, Object> parameters
    ) throws BrillienException {
        String reference = isReference( to ) ? getReference( to ) : null;

        BrillienCommunication rpc = presenceManager.getMediator().prepareNewCommunication(
                sharedMessage,
                this.getPresenceManager().getMediator().mediatorEntity(),
                this.getFlowID(),
                reference == null ? to : reference,
                message, messageType, parameters
        );
        if( redirectEntityName != null )
            rpc.setRedirectEntityName( redirectEntityName );
        if( redirectMessage != null )
            rpc.setRedirectMessage( redirectMessage );

        if( !to.equalsIgnoreCase("logger") )
            systemlog( "Sending " + messageType + " message to " + to + " about " + message + "::", new InnerList<Object>( rpc ) );

        return rpc;
    }

    @Override
    public void sendResponse( BrillienCommunication t, Object response ) throws BrillienException{
        BrillienCommunication respT = t.prepareForResponse(response);

        systemlog( "Sending Response message::", new InnerList<Object>( t.toString() ) );

        presenceManager.getMediator().sendCommunication( respT );
    }

    @Override
    public void sendError( BrillienCommunication t, String message ) throws BrillienException{
        BrillienCommunication respT = t.prepareForError(message);

        systemlog( "Sending Error message::", new InnerList<Object>( t.toString() ) );

        presenceManager.getMediator().sendCommunication( respT );
    }

    @Override
    public void sendError( BrillienCommunication t, int errorCode, String message ) throws BrillienException{
        sendError( t, errorCode, message, null );
    }

    @Override
    public void sendError( BrillienCommunication t, int errorCode, String message, Object value ) throws BrillienException{
        BrillienCommunication respT = t.prepareForError(errorCode, message, value);

        systemlog( "Sending Error message::", new InnerList<Object>( t.toString() ) );

        presenceManager.getMediator().sendCommunication( respT );
    }


    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    //              UNIT SEND METHODS
    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------


    @Override
    public BrillienCommunication sendUnitGet( String to, String message, Map<String, Object> parameters ) throws BrillienException{
        return sendGet(to, "processUnitGet", new InnerMap<String,Object>( "to", to, "message", message, "parameters", parameters) );
    }

    @Override
    public BrillienCommunication sendUnitGet( int timeout, String to, String message, Map<String, Object> parameters ) throws BrillienException{
        return sendGet(to, "processUnitTGet", new InnerMap<String,Object>( "timeout", timeout, "to", to, "message", message, "parameters", parameters) );
    }

    @Override
    public void sendUnitAGet( String to, String message, String redirectMessage, Map<String, Object> parameters ) throws BrillienException{
        sendAGet(to, "processUnitAGet", redirectMessage, new InnerMap<String,Object>( "to", to, "message", message, "redirectMessage", redirectMessage, "parameters", parameters)) ;
    }

    @Override
    public void sendUnitDelegatedSet( String to, String message, String redirectEntityName, String redirectMessage, Map<String, Object> parameters ) throws BrillienException{
        sendDelegatedSet(to, "processUnitDelegatedSet", redirectEntityName, redirectMessage, new InnerMap<String,Object>( "to", to, "message", message, "redirectEntityName", redirectEntityName, "redirectMessage", redirectMessage, "parameters", parameters) );
    }

    @Override
    public void sendUnitSet( String to, String message, Map<String, Object> parameters ) throws BrillienException{
        sendSet(to, "processUnitSet", new InnerMap<String,Object>( "to", to, "message", message, "parameters", parameters) );
    }

    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    //              HELPER METHODS
    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------


    @Override
    public boolean isWaitingForResponse(){
        return waitingForResponse;
    }

    public Object processMessage(String message, Object... parameters) throws BrillienException {
        return null;
    }


    @Override
    public boolean hasMessageProcessorReturnValue( @P(name = "messageProcessorName") String messageProcessorName ) throws BrillienException{
        try {
            Method m = ReflectionServices.getMethod( this.getClass(), messageProcessorName );
            if( m == null )
                throw new BrillienException("MessageProcessor does not exist!");

            return !m.getReturnType().equals( Void.TYPE );
        } catch (BrillienException e) {
            throw e;
        } catch (Exception e) {
            throw new BrillienException( e.getMessage() );
        }
    }

    @Override
    public List<String> getMessageProcessorParameterTypes( @P(name = "messageProcessorName") String messageProcessorName ) throws BrillienException{
        try {
            Method m = ReflectionServices.getMethod( this.getClass(), messageProcessorName );
            if( m == null )
                throw new BrillienException("MessageProcessor does not exist!");

            Class[] classes = m.getParameterTypes();
            List<String> parameterTypes = new LinkedList<String>();

            for( Class type : classes )
                parameterTypes.add( type.getName() );

            return parameterTypes;
        } catch (BrillienException e) {
            throw e;
        } catch (Exception e) {
            throw new BrillienException( e.getMessage() );
        }
    }

    @Override
    public List<String> getMessageProcessorParameterNames( @P(name = "messageProcessorName") String messageProcessorName ) throws BrillienException{
        try {
            return ReflectionServices.getFormalParameterNames( ReflectionServices.getMethod( this.getClass(), messageProcessorName ) );
        } catch (StreamLineException e) {
            throw new BrillienException( e.getErrorCode(), e.getMessage() );
        } catch (Exception e) {
            throw new BrillienException( e.getMessage() ); 
        }
    }

    protected String getToStringPrefix(){
        return "Presence";
    }

    @Override
    public String toString() {
        return getToStringPrefix() + "{" +
                "type='" + getClass().getSimpleName() + '\'' +
                ", Instance=" + getName() +
                ", FlowID=" + getFlowID() +
                '}';
    }

}
